#ifndef __EPSILON__TIMER_H__
#define __EPSILON__TIMER_H__

#ifdef WIN32
#  include <windows.h>
#  include <mmsystem.h>
#  ifdef  _MSC_VER
#    pragma comment (lib, "winmm.lib")
#  endif
#else
#  include <sys/time.h>
#endif

#include <math.h>

/**
 * Epsilon's High Resolution Timer
 *
 *   @ TODO: This class does too much...
 *
 *            Frame timing ought to be limited to
 *             eFrameTimer ONLY.
 *
**/
class eTimer {
public:
  eTimer (void) {
    has_hardware_timer = false;

    init ();

    freq   = 1 / 1000.0f;
    s      = 0;
    paused = false;

#ifdef WIN32
    if (! has_hardware_timer)
      timeBeginPeriod (1); 
#endif
  }

  ~eTimer (void) {
#ifdef WIN32
    if (! has_hardware_timer)
      timeEndPeriod (1);
#endif
  }

  void init (void)
  {
#ifdef WIN32
    __int64 perf_freq;

    // Is the Hardware Timer supported?
    if (QueryPerformanceFrequency ((LARGE_INTEGER *)&perf_freq) && perf_freq > 0) {
      cpu_frequency      = (double)perf_freq;
      has_hardware_timer = true;
    }
#else /* UNIX */
    cpu_frequency        = 0.0;
    has_hardware_timer   = false;
#endif
  }

  int milliseconds (void)
  {
    static bool initialized = false;
    static int  msecs;

#ifdef WIN32
    static __int64  start_count; // Performance Counter at FIRST call to milliseconds
           __int64  perf_count;  // Performance Counter NOW

    if (has_hardware_timer)
    {
      QueryPerformanceCounter ((LARGE_INTEGER *)&perf_count);

      if (! initialized) {
        initialized = true;
        start_count = perf_count;
        return 0;
      }

      msecs = (int)(1000.0f * (perf_count - start_count) / cpu_frequency);
      return msecs;
    }

    msecs = timeGetTime ();
#else /* UNIX */
    struct timeval  tv;
    struct timezone tz;

    static int start_time;

    gettimeofday (&tv, &tz);

    if (! initialized) {
      initialized = true;
      
      start_time = tv.tv_sec;
      return tv.tv_usec / 1000;
    }

    msecs = (tv.tv_sec - start_time) * 1000 + tv.tv_usec / 1000;
#endif

    return msecs;
  }

  float milliseconds_float (void)
  {
    static bool  initialized = false;
    static float msecs;

#ifdef WIN32
    static __int64  start_count; // Performance Counter at FIRST call to milliseconds_float
           __int64  perf_count;  // Performance Counter NOW

    if (has_hardware_timer)
    {
      QueryPerformanceCounter ((LARGE_INTEGER *)&perf_count);

      if (! initialized) {
        initialized = true;
        start_count = perf_count;
        return 0.0f;
      }

      msecs = (float)(1000.0f * (perf_count - start_count) / cpu_frequency);
      return msecs;
    }

    msecs = 0.0f;
#else /* UNIX */
    struct timeval  tv;
    struct timezone tz;

    static float start_time;

    gettimeofday (&tv, &tz);

    if (! initialized) {
      initialized = true;
      
      start_time = tv.tv_sec;
      return (float)tv.tv_usec / 1000.0f;
    }

    msecs = ((float)tv.tv_sec - start_time) * 1000.0f + (float)tv.tv_usec / 1000.0f;
#endif

    return msecs;
  }

  void start (void) {
    s                     = milliseconds ();
    last_frame_time       = frame_interval       = fps_time = 0.0f;
    last_frame_time_msecs = frame_interval_msecs = frames   = 0;

    current_time_offset = 0.0f;
  }

  float tick  (void) {
    float time = (float)(milliseconds () - s) * freq;

    frame_interval        = time - last_frame_time;
    last_frame_time       = time;   // last_frame_time will always be equal to time, except on the first frame after pause

    current_time_msecs    = milliseconds ();
    frame_interval_msecs  = current_time_msecs - last_frame_time_msecs;
    last_frame_time_msecs = current_time_msecs;
    frames++;

    if (! paused) {
      current_time         = time;
      current_time_offset += frame_interval;
    }

    return time;
  }

  /// Pause / Unpause or Toggle Pause...
  void pause (const int& req_op = -1) {
    if (req_op > 0)
      paused = true;
    else if (req_op < 0)
      paused = ! paused;
    else
      paused = false;
  }


  // Selectors
  float        time              (void) const { return current_time;          }  ///< Seconds between last unpaused tick() and game start
  float        TimeOffset        (void) const { return current_time_offset;   }  ///< Same as above, except takes into account time spent paused.
  inline float LastFrameTime     (void)       { return last_frame_time;       }  ///< Exactly same return value as time()
  inline DWORD LastFrameTimeMsec (void)       { return last_frame_time_msecs; }  ///< Msec. between last tick() and OS start
  inline float FrameInterval     (void)       { return frame_interval;        }  ///< Seconds between this frame and last
  inline DWORD FrameIntervalMsec (void)       { return frame_interval_msecs;  }  ///< Msec. between this frame and last
  inline float FPSTime           (void)       { return fps_time;              }
  inline int   Frames            (void)       { return frames;                }
  inline bool  IsPaused          (void) const { return paused;                }

  // Mutators
  inline void SetFrames  (int value) { frames   =        value; };
  inline void SetFPSTime (int value) { fps_time = (float)value; };

private:
  DWORD  s;
  float  freq;
  float  current_time;
  float  current_time_offset;
  float  last_frame_time;
  float  frame_interval;
  DWORD  current_time_msecs;
  DWORD  last_frame_time_msecs;
  DWORD  frame_interval_msecs; // frame interval in msecs
  float  fps_time;
  int    frames;
  bool   paused;

  bool   has_hardware_timer;
  double cpu_frequency;
};


/**
 * A Simple Interval Timer
 *
 *   -*- Now with re-entrant thread-safety goodness!
**/
class eFrameTimer {
public:
  eFrameTimer (void) : _fps           (0.0f),
                       frame_time     (0.0f),
                       frame_interval (0.0f),
                       fps_time       (0.0f),
                       frames         (0) { };

  void Begin (void) {
    float time     = timer.tick ();
    frame_interval = time - frame_time;
    frame_time     = time;

    if (frame_time - fps_time > 1.0f) {
      _fps = frames / (frame_time - fps_time);

      fps_time = frame_time;
      frames   = 0;
    }
  }

  void End (void) {
    frames++;
  }

  float FPS (void) {
    return _fps;
  }

  float FrameInterval (void) {
    return frame_interval;
  }

private:
  float  frame_time;     ///< The starting time of the LAST frame
  float  frame_interval; ///< The interval between the LAST frame and THIS frame
  float  fps_time;       ///< The last time the FPS was calculated
  int    frames;         ///< The number of frames between FPS calculations
  float  _fps;           ///< Cached FPS
  eTimer timer;          ///< The actual timer that does the work...
};


#endif /* __EPSILON__TIMER_H__ */
